package org.gazzax.labs.solrwithmarc.index.processors;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.update.AddUpdateCommand;
import org.apache.solr.update.processor.UpdateRequestProcessor;
import org.apache.solr.update.processor.UpdateRequestProcessorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Tag 007 processor factory.
 * It will remove any 007 tag and subsequently produce several tags for each subsection, according with 
 * the specs in the following link: http://www.loc.gov/marc/bibliographic/bd007.html 
 * 
 * @author agazzarini
 * @since 1.0
 */
public class _007PhysicalDescriptionProcessorFactory extends UpdateRequestProcessorFactory 
{
	private final static Logger LOGGER = LoggerFactory.getLogger(_007PhysicalDescriptionProcessor.class);

	private static final String _007_TAG_NAME = "007";
	private static final Map<Character, int [][]> RULES = new HashMap<Character, int[][]>();
	static 
	{
		RULES.put('a', new int [][] {{1},{2},{3},{4},{5},{6},{7}});
		RULES.put('c', new int [][] {{1},{2},{3},{4},{5},{6,8},{9},{10},{11},{12},{13}});
		RULES.put('d', new int [][] {{1},{2},{3},{4},{5}});
		RULES.put('f', new int [][] {{1},{2},{3,4},{5},{6,8},{9}});		
		RULES.put('g', new int [][] {{1},{2},{3},{4},{5},{6},{7},{8}});
		RULES.put('h', new int [][] {{1},{2},{3},{4},{5},{6,8},{9},{10},{11},{12}});
		RULES.put('k', new int [][] {{1},{2},{3},{4},{5}});
		RULES.put('m', new int [][] {{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17,22}});		
		RULES.put('o', new int [][] {{1}});
		RULES.put('q', new int [][] {{1}});
		RULES.put('r', new int [][] {{1},{2},{3,4},{5},{6},{7},{8},{9,10}});		
		RULES.put('s', new int [][] {{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13}});
		RULES.put('t', new int [][] {{1}});
		RULES.put('v', new int [][] {{1},{2},{3},{4},{5},{6},{7},{8}});
		RULES.put('z', new int [][] {{1}});
	}
	
	/**
	 * Tag 007 processor.
	 * It will remove any 007 tag and subsequently produce several tags for each subsection, according with 
	 * the specs in the following link: http://www.loc.gov/marc/bibliographic/bd007.html 
	 * 
	 * @author agazzarini
	 * @since 1.0
	 */
	class _007PhysicalDescriptionProcessor extends UpdateRequestProcessor
	{
		/**
		 * Builds a new processor.
		 * 
		 * @param next the next processor in the chain.
		 */
		_007PhysicalDescriptionProcessor(final UpdateRequestProcessor next) 
		{
			super(next);
		}
		
		@Override
		public void processAdd(final AddUpdateCommand command) throws IOException 
		{
			final SolrInputDocument document = command.getSolrInputDocument();
			final SolrInputField _007 = document.removeField("007");
			if (_007 != null)
			{
				for (Object description : _007.getValues())
				{
					String physicalDescription = (String) description;
					if (physicalDescription != null && physicalDescription.trim().length() != 0)
					{
						extractDataElements(physicalDescription, document);
					}
				}
			}
			super.processAdd(command);
		}

		/**
		 * Extracts the data found in the given physical description.
		 * 
		 * @param physicalDescription the physical description.
		 * @param document the target SOLR document.
		 */
		private void extractDataElements(final String physicalDescription, final SolrInputDocument document) 
		{
			int [][] rule = RULES.get(physicalDescription.charAt(0));
			if (rule == null)
			{
				LOGGER.warn("Unknown category of material found: " + physicalDescription);
				return;
			}
			
			for (int [] posArray : rule)
			{
				if (posArray.length == 1)
				{
					int pos = posArray[0];
					
					try 
					{
						String fieldName = getFieldName(pos);
						char fieldValue = getFieldValue(pos, physicalDescription);
						if (fieldValue != '#' && fieldValue != '|') 
						{ 
							addField(fieldName, fieldValue, document);
						}
					} catch (Exception e) 
					{
						LOGGER.warn("Wrong position: " + pos + " in " + physicalDescription, e);
					}
				} else
				{
					int start = posArray[0];
					int end = posArray[1];
					
					try 
					{
						addField(getFieldName(start, end), getFieldValue(start, end, physicalDescription), document);	
					} catch (Exception e) 
					{
						LOGGER.warn("Wrong position: " + start+ "-" + end + " in " + physicalDescription);
					}					
				}
			}
		}
	};
		
	@Override
	public UpdateRequestProcessor getInstance(
			final SolrQueryRequest req, 
			final SolrQueryResponse rsp, 
			final 
			UpdateRequestProcessor next) 
	{
		return new _007PhysicalDescriptionProcessor(next);
	}
	
	/**
	 * Returns the field name for a single-position field.
	 * 
	 * @param pos the position.
	 * @return the field name for a single-position field.
	 */
	private String getFieldName(int pos)
	{
		return new StringBuilder(_007_TAG_NAME)
			.append("_")
			.append(pos < 10 ? "0" : "")
			.toString();
	}

	/**
	 * Returns the field name for a multi-position field.
	 * 
	 * @param start the start position.
	 * @param start the end position.
	 * @return the field name for a multi-position field.
	 */
	private String getFieldName(final int start, final int end)
	{
		return new StringBuilder(_007_TAG_NAME)
			.append("_")
			.append(start < 10 ? "0" : "")
			.append("-")
			.append(end < 10 ? "0" : "")
			.toString();
	}
	
	/**
	 * Returns the field value for a single-position field.
	 * 
	 * @param pos the position.
	 * @param description the physical description.
	 * @return the field value for a single-position field.
	 */
	private char getFieldValue(final int pos, final String description)
	{
		return description.charAt(pos);
	}

	/**
	 * Returns the field value for a multi-position field.
	 * 
	 * @param start the start position.
	 * @param start the end position.
	 * @param description the physical description.
	 * @return the field value for a multi-position field.
	 */
	private String getFieldValue(final int start, final int end, final String description)
	{
		return description.substring(start, end + 1);
	}	
	
	/**
	 * Adds the given field to the SOLR document.
	 * Field is multivalued but although SOLR underlying collection model allows duplicates, 
	 * a check is made in order to see if the value if already there. In that case the value won't be collected.
	 * 
	 * @param name the field name.
	 * @param value the field value.
	 * @param document the {@link SolrInputDocument}.
	 */
	private void addField(final String name, final Object value, final SolrInputDocument document)
	{
		if ((document.getFieldValues(name) == null || !document.getFieldValues(name).contains(value))) 
		{
			document.addField(name, value);							
		}		
	}
}